<template>
  <div style="padding-left: 100px">
    <button @click="setWatermark">设置水印</button>
    <button @click="removeWatermark">移除水印</button>
  </div>
</template>

<script lang="ts" setup>
interface InterfaceWatermarkOptions {
  // 旋转角度 默认值：45
  rotate?: string
  // 文本透明度 默认值：0.1
  opacity?: string
  // 文本大小 默认值：16
  fontSize?: string
  // 文本加粗 默认值：normal
  fontWeight?: string
  // 文本距离外边的 padding 默认值：20
  padding?: number
  // 文本间距 默认值：8
  space?: number
  // 文本颜色 使用16进制颜色会渲染不出来，原因未知 默认值：rgb(0,0,0)
  color?: string
}

interface InterfaceText {
  x: number
  y: number
  text: string
  width: number
}

class Watermark {

  watermarkOption?: InterfaceWatermarkOptions
  watermarkText?: string | string[]
  watermarkId?: string
  observer?: MutationObserver
  watermarkDom?: Element

  static observerOptions = {
    childList: true,  // 观察目标子节点的变化，是否有添加或者删除
    attributes: true, // 观察属性变动
    subtree: true     // 观察后代节点，默认为 false
  }

  static observerBodyOptions = {
    childList: true,
  }

  constructor() {
  }

  /** 创建水印，并且渲染到 body 下
   *
   * @param watermarkText
   * @param watermarkOption
   */
  render(watermarkText: string | string[], watermarkOption?: InterfaceWatermarkOptions) {
    watermarkOption = watermarkOption || {}
    this.watermarkOption = watermarkOption

    const {
      rotate = '45',
      opacity = '0.1',
      fontSize = '16px',
      fontWeight = 'normal',
      padding = 20,
      space = 8,
      color = 'rgb(0,0,0)',
    } = watermarkOption

    if (this.watermarkId && document.querySelector('#' + this.watermarkId)) {
      this.removeWatermark()
    }

    const dom = document.createElement('div')
    dom.id = this.createDomId()
    dom.style.width = '100vw'
    dom.style.height = '100vh'
    dom.style.position = 'fixed'
    dom.style.zIndex = '9999'
    dom.style.left = '0'
    dom.style.top = '0'
    dom.style.pointerEvents = 'none'

    if (typeof watermarkText === "string") {
      watermarkText = [watermarkText]
    }

    this.watermarkText = watermarkText

    let bgSetting = {
      height: 0,
      width: 0,
      text: [] as InterfaceText[],
      g: {
        x: 0,
        y: 0,
        centerX: 0,
        centerY: 0,
        transform: '',
      },
      contentWidth: 0,
      contentHeight: 0,
      textStyle: '',
    }

    bgSetting = watermarkText.reduce((a, b) => {
      const rect = this.getTextSize(b, watermarkOption)

      a.text.push({
        x: 0,
        y: a.contentHeight + space,
        text: b,
        width: rect.width,
      })
      a.contentHeight += (rect.height + space)
      a.contentWidth = Math.max(a.contentWidth, rect.width)

      return a
    }, bgSetting)

    // 计算对角线长度
    bgSetting.width = Math.hypot(bgSetting.contentWidth + 2 * padding, bgSetting.contentHeight + 2 * padding)
    // 高度直接使用对角线长度
    bgSetting.height = bgSetting.width

    // 文本居中
    bgSetting.text.forEach(v => {
      v.x = (bgSetting.contentWidth - v.width) / 2
    })

    // 内容居中
    bgSetting.g.x = (bgSetting.width - bgSetting.contentWidth) / 2
    bgSetting.g.y = (bgSetting.height - bgSetting.contentHeight) / 2
    bgSetting.g.centerX = bgSetting.width / 2
    bgSetting.g.centerY = bgSetting.height / 2
    bgSetting.g.transform = `rotate(${rotate},${bgSetting.g.centerX},${bgSetting.g.centerY}),translate(${bgSetting.g.x},${bgSetting.g.y})`

    bgSetting.textStyle = `font-weight: ${fontWeight};font-size: ${fontSize};opacity: ${opacity};color: red;`

    const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${bgSetting.width}" height="${bgSetting.height}"><g transform="${bgSetting.g.transform}">${
      bgSetting.text.map(({x, y, text}) => {
        return `<text stroke="${color}" style="${bgSetting.textStyle}" x="${x}" y="${y}">${text}</text>`
      }).join('')
    }</g></svg>`

    dom.style.backgroundImage = `url('data:image/svg+xml;utf8,${svg}')`

    document.body.appendChild(dom)
    this.watermarkDom = dom

    const observer = this.initObserver()
    observer.observe(dom, Watermark.observerOptions)
    observer.observe(document.body, Watermark.observerBodyOptions)
  }

  /** 初始化响应器
   *  使用 MutationObserver 进行简单的防破解处理
   */
  initObserver() {
    this.observer = new MutationObserver(mutationsList => {
      for (const mutation of mutationsList) {
        if (mutation.type === 'childList') {
          const removedNodes = mutation.removedNodes
          if (Array.from(removedNodes).some(v => (v as Element).id === this.watermarkId)) {
            this.resetWatermark()
          }
        } else if (mutation.type === 'attributes') {
          this.resetWatermark()
        }
      }
    })
    return this.observer
  }

  /** 获取文本渲染后的尺寸
   *
   * @param text
   * @param watermarkOption
   */
  getTextSize(text: string, watermarkOption?: InterfaceWatermarkOptions) {
    const {
      fontSize = '16px',
      fontWeight = 'normal',
    } = watermarkOption || {}
    const span = document.createElement('span')
    span.innerText = text
    span.style.fontSize = fontSize
    span.style.fontWeight = fontWeight
    document.body.appendChild(span)
    const rect = span.getBoundingClientRect()
    document.body.removeChild(span)
    return rect
  }

  /**
   * 创建水印dom的唯一id
   */
  createDomId() {
    this.watermarkId = `watermark-${String(Math.random()).slice(2)}`
    return this.watermarkId
  }

  /**
   * 重置水印
   */
  resetWatermark() {
    this.removeWatermark()

    if (this.watermarkText) {
      this.render(this.watermarkText, this.watermarkOption)
    }
  }

  /**
   * 移除水印
   */
  removeWatermark() {
    if (this.observer) {
      this.observer.disconnect()

      const dom = document.querySelector('#' + this.watermarkId)
      if (dom) {
        document.body.removeChild(dom)
      }
    }
  }
}

const watermark = new Watermark()

function setWatermark() {
  watermark.render(['好好学习，天天向上', '2021-12-08 16:33:25'])
}

function removeWatermark() {
  watermark.removeWatermark()
}
</script>

<style scoped lang="less">

</style>
